package reesoft.idgenerator.service.idstore;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reesoft.common.code.ErrorCode;
import reesoft.common.util.ApplicationContextUtil;
import reesoft.common.util.StringUtil;
import reesoft.idgenerator.IdGenerateException;
import reesoft.idgenerator.IdGeneratorErrorCode;

import java.io.*;
import java.nio.channels.OverlappingFileLockException;
import java.util.Properties;

/**
 * 以属性文件为媒介存储序号，适用于单机或通过网络共享文件的环境
 *
 * Created by reesoft on 2017-08-28
 */
public class FileIdStore extends BaseIdStore
{
    private static final Logger LOG = LoggerFactory.getLogger(FileIdStore.class);

    /**
     * 存储序号的文件路径
     */
    private String filePath;

    /**
     * 构造函数
     */
    public FileIdStore()
    {
        FileIdStoreConfig config = ApplicationContextUtil.getBean(FileIdStoreConfig.class);

        if (config != null)
        {
            setFilePath(config.getFilePath());
        }
    }

    /**
     * 设置存储序号的文件路径
     */
    public void setFilePath(String filePath)
    {
        this.filePath = filePath;
    }

    @Override
    public void init(String name, long initValue, long maxValue) throws IdGenerateException
    {
        LOG.debug("FileIdStore.init, name:{}, initValue:{}, maxValue:{}", name, initValue, maxValue);

        synchronized (this)
        {
            if (StringUtil.isBlank(filePath))
            {
                throw new IdGenerateException("File path is not set");
            }

            if (StringUtil.isBlank(name))
            {
                throw new IdGenerateException(ErrorCode.INVALID_PARAMETER);
            }

            setMaxValue(maxValue);

            RandomAccessFile randomAccessFile = null;

            try
            {
                // 以独占方式打开文件并锁定
                randomAccessFile = new RandomAccessFile(new File(filePath), "rws");
                randomAccessFile.getChannel().lock();

                Properties properties = new Properties();
                properties.load(new FileInputStream(randomAccessFile.getFD()));

                String existingValue = properties.getProperty(name);

                if (existingValue != null
                        && !existingValue.isEmpty())
                {
                    // 解析文件中存储的值是否有效，如果无效将抛出异常
                    Long.parseLong(existingValue);
                    return;
                }

                properties.setProperty(name, String.valueOf(initValue));

                FileOutputStream fileOutputStream = new FileOutputStream(randomAccessFile.getFD());
                fileOutputStream.getChannel().truncate(0);
                properties.store(fileOutputStream, "Non assigned IDs");

                LOG.info("FileIdStore.init, create ID store with name:{}, initValue:{}", name, initValue);
            }
            catch (OverlappingFileLockException e)
            {
                throw new IdGenerateException("Failed to lock file");
            }
            catch (IOException e)
            {
                throw new IdGenerateException("Failed to access file");
            }
            catch (NumberFormatException e)
            {
                throw new IdGenerateException("Invalid file format");
            }
            catch (Exception e)
            {
                throw new IdGenerateException(e.toString());
            }
            finally
            {
                if (randomAccessFile != null)
                {
                    try
                    {
                        randomAccessFile.close();
                    }
                    catch (IOException e)
                    {
                        throw new IdGenerateException("Failed to access file");
                    }
                }
            }
        }
    }

    @Override
    public boolean check(String name)
    {
        //LOG.debug("FileIdStore.check, name:" + name);

        synchronized (this)
        {
            if (StringUtil.isBlank(filePath))
            {
                return false;
            }

            if (StringUtil.isBlank(name))
            {
                return false;
            }

            File file = new File(filePath);
            FileInputStream fileInputStream = null;

            try
            {
                Properties properties = new Properties();
                fileInputStream = new FileInputStream(file);
                properties.load(fileInputStream);

                String existingValue = properties.getProperty(name);

                if (existingValue != null
                        && !existingValue.isEmpty())
                {
                    // 解析文件中存储的值是否有效，如果无效将抛出异常
                    Long.parseLong(existingValue);
                    return true;
                }
            }
            catch (IOException | OverlappingFileLockException | NumberFormatException e)
            {
                return false;
            }
            finally
            {
                if (fileInputStream != null)
                {
                    try
                    {
                        fileInputStream.close();
                    }
                    catch (IOException e)
                    {
                        e.printStackTrace();
                    }
                }
            }
        }

        return false;
    }

    @Override
    public long loadAndIncrease(String name, long amount) throws IdGenerateException
    {
        LOG.info("FileIdStore.loadAndIncrease, name:{}, amount:{}", name, amount);

        synchronized (this)
        {
            if (StringUtil.isBlank(filePath))
            {
                throw new IdGenerateException("File path is not set");
            }

            if (StringUtil.isBlank(name)
                    || amount <= 0)
            {
                throw new IdGenerateException(ErrorCode.INVALID_PARAMETER);
            }

            RandomAccessFile randomAccessFile = null;

            try
            {
                randomAccessFile = new RandomAccessFile(new File(filePath), "rws");
                randomAccessFile.getChannel().lock();

                Properties properties = new Properties();
                properties.load(new FileInputStream(randomAccessFile.getFD()));

                String existingValue = properties.getProperty(name);

                long currentValue;

                if (existingValue != null
                        && !existingValue.isEmpty())
                {
                    currentValue = Long.valueOf(existingValue);

                    if (Long.compareUnsigned(currentValue + amount, getMaxValue()) == 1)
                    {
                        throw new IdGenerateException(IdGeneratorErrorCode.NO_RESOURCE_TO_ASSIGN);
                    }

                    properties.setProperty(name, String.valueOf(currentValue + amount));
                }
                else
                {
                    throw new IdGenerateException(IdGeneratorErrorCode.ID_NOT_REGISTERED);
                }

                FileOutputStream fileOutputStream = new FileOutputStream(randomAccessFile.getFD());
                fileOutputStream.getChannel().truncate(0);
                properties.store(fileOutputStream, "Non assigned IDs");

                LOG.info("FileIdStore.loadAndIncrease, ID assigned with name:{}, currentValue:{}, amount:{}",
                        name, currentValue, amount);

                return currentValue;
            }
            catch (OverlappingFileLockException e)
            {
                throw new IdGenerateException("Failed to lock file");
            }
            catch (IOException e)
            {
                throw new IdGenerateException("Failed to access file");
            }
            catch (NumberFormatException e)
            {
                throw new IdGenerateException("Invalid file format");
            }
            catch (Exception e)
            {
                throw new IdGenerateException(e.toString());
            }
            finally
            {
                if (randomAccessFile != null)
                {
                    try
                    {
                        randomAccessFile.close();
                    }
                    catch (IOException e)
                    {
                        throw new IdGenerateException("Failed to access file");
                    }
                }
            }
        }
    }
}
